cmath (random)

수치
#include <cmath>
복소수(complex)
Mandelbrot Set(망델브로 집합)
z_0(c)=c, z_n+1(c)=z_n^2+c, abs(z_n(c))<=2

망델브로 집합 복소수 연산에서 제곱근을 계산하는 연산이 가장 큰 비용이 든다.
norm 함수는 complex의 abs의 제곱, 즉 sqrt없이 abs 연산을 제공한다.
std::norm
returns the norm value of the complex number x
template <class T>
T norm(const complex<T>& x); // complex
double norm(AritheticTpye x); // overload;
madelbrot set
class mandel_pixel{
public:
mandel_pixel(SDL_Surface* screen, int x, int y, int xdim, int ydim, int max_iter): screen(screen), max_iter(max_iter), iter(0), c(x, y){
c*=2.4f/static_cast<float>(ydim);
c-=complex<float>(1.2*xdim/ydim+0.5, 1.2);
iterate();
}
int iterations() const{ return iter; }
uint32_t color() const { //... }
private:
void iterate(){
complex<float> z=c;
for(; iter<max_iter&&norm(z)<=4.0f; iter++) z=z*z+c;
}
// ...
int iter;
complex<float> c;
};
complex<T>는 동일한 complex<T> 타입이나, T 타입과만 opeartor+, operator-를 지원한다.
complex<double> z(3, 5), c=2*z; // compile error; int*complex<double>
template <typename T>
inline T twice(const T& z){
return 2*z;
}
int main(void){
complex<double> z(3, 5), c;
c=twice(z); // compile error int*complex<double>
}
내부적으로 곱해주는 타입도 템플릿으로 지정해주는 것이 좋다.
template <typename T>
inline T twice(const T& z){
return T{2}*z;
}
단 위와 같이 코딩을 하면, 실수 2에 대하여 불필요하게 complex<double>로 인스턴스화 한다.
가능하다면, 오버로드로 추가로 구현하는 것이 좋음
난수 생성기
#include <random>

난수 생성기는 컴퓨터 시뮬레이션, 게임 프로그래밍, 암호화등 많은 애플리케이션에서 사용되며,
대부분의 프로그래밍 언어에서는 난수 생성기를 제공한다.

대부분의 난수 생성기는 의사 난수 계산(pseudo-random calculation)을 기반으로 한다.
내부적으로 시드(seed)를 가진다.
시드를 통해 난수를 요청할 때마다 결정론적 계산을 통해 값이 변한다.

의사 난수 생성기(Pseudo_Random Number Generator, PRNG)는
동일한 시드로 시작하면 항상 동일한 시퀀스를 반환
C++11 이전에는 기존 C의 rand와 srand 함수만 존재하였다.
C++11에서는 고품질의 <random> 라이브러리가 추가되었다.

rand와 srand는 남수 품질이 떨어지며, 사용이 권장되지 않음
Walter Brown Rnadom Number(C++ 표준은 아님)
#include <random>
std::default_random_engine& global_urng(){
static std::default_random_engine u{};
return u;
}
void randomize(){
static std::random_device rd{};
global_urng().seed(rd());
}
int pick(int from, int thru){
static std::uniform_int_distribution<> d{};
using parm_t=decltype(d)::param_type;
return d(global_urng(), parm_t{from, thru});
}
double pick(double from, doble upto){
static std::uniform_real_distribution<> d{};
suing parm_t=decltype(d)::param_type;
return d(global_urng(), parm_t{from, upto});
}
- randomize: 생성기의 시드를 초기화해 다음 번호를 무작위로 만든다.
- pick(int a, int b): a와 b가 int일 때, 구간 [a, b)에 있는 int 값 하나를 선택
- pick(double a, double b): a와 b가 double 일 때, 오른쪽 개구간 [a, b)에 있는 double 값 하나를 선택
#include <iostream>
#include <random>
int main(void){
randomize();
cout<<"Now, we roll dice:\n";
for(int i=0; i<15; ++i) cout<<pick(1, 6)<<endl;
cout<<"\nLet's roll continuous dice now: ;-)\n";
for(itn i=0; i<16; ++i) cout<<pcik(1.0, 6.0)<<endl;
}
무작위 테스트
random 사용 예시

complex가 분배 법칙을 성립하는지 테스트하는 경우
// operator*
inline complex operator*(const complex& c1, const complex c2){
return complex(real(c1)*real(c2)-imag(c1)*imag(c2), real(c2)*imag(c2)+imag(c1)*real(c2));
}
반올림 오류를 대처하기 위해 상대 오차를 계산하는 similar 함수를 생성
#include <limits>
const double eps=10*numeric_limits<double>::epsilon();
inline bool similar(complex x, complex y){
double sum=abs(x)+abs(y);
if(sum<1000*numeric_limits<double>::min()) return true;
return abs(x-y)/sum<=eps;
}
0으로 나눔을 피하기 위해서 double의 표현 가능한 최솟값의 1,000배보다 작을 시, true return
struct distributivity_violated{};
inline void test(complex a, complex b, complex c){
if(!similar(a*(b+c), a*b+a*c)){
cerr<<"Test detected that "<<a<< //...
throw distributivity_violated();
}
}
const double from=-10.0, upto=10.0;
inline complex mypick(void){
return complex(pick(from, upto), pick(from, upto));
}
int main(void){
const int max_test=20;
randomize();
for(int i=0; i<max_test; ++i){
complex a=mypick();
for(int j=0; j<max_test, ++j){
complex b=mypcik();
for(int k=0; k<max_test; ++k){
complex c=mypcik();
test(a, b, c);
}
}
}
}
난수 생성 엔진
<random> 라이브러리는 두 종류의 기능적 개체, 생성기(generator)와 분포(distribution)를 포함한다.
생성기는 부호 없는 정수의 시퀀스를 생성한다.(정확한 타입은 각 생성기의 typedef에서 제공)
모든 값은 동일한 확률을 가짐(uniform distribution)

분포(distribution) 개체는 생성된 값들을 매개변수화된 분배에 해당하는 값으로 매핑한다.
특별한 분포가 필요없을 때는 default_random_engine 사용

난스 시퀀스는 결정적으로 시드값에 의해 결정된다.
각 엔진은 개체를 생성할 때마다 동일한 시드로 초기화됨
주어진 타입의 새로운 엔진은 동일한 seed에 대하여 동일한 시퀀스를 생성
void random_number(void){
default_random_engine re;
cout<<"Random numbers: ";
for(int i=0; i<4; ++i){
cout<<re<<i<3?", ":"");
}
cout<<'\n';
}
int main(void){
random_numbers();
random_numbers();
}
위와 같이 선언하면, random_number() 함수가 호출될 때마다, 새롭게 default_random_engine을 생성하고
파기한다.

동일한 시퀀스를 반복적으로 출력함

이를 피하기 위해서는 static으로 선언해 영구 엔진으로 만들어야 한다.
void random_numbers(void){
static random_device rd;
static default_random_engine re(rd());
}
random_device는 하드웨어 및 운영체제 이벤트의 측정에 의존하는 값을 반환한다.
(거의 무작위 수를 반환, 매우 높은 엔트로피(entropy)를 가짐)
내부적으로 random_device는 시드를 설정할 수 없는 random_engine과 동일함

random_device는 하드웨어 및 파일 시스템에 접근이 필요함으로 성능저하를 야기할 수 있다.
암호화 같은 고품질 난수에 의존하지 않는다면, static으로 선언해서 한번만 처리해주는 것이 좋음
generator

난수 생성 기본 엔진
- linear_congruential_engine
- mersenne_twister_engine
- subtract_with_carry_engine

다른 엔진으로부터 새로운 엔진을 만드는 엔진 어댑터
- discard_block_engine    : 매번 기본 엔진에서 n개의 항목을 무시
- independent_bits_engine    : 주요 난수를 w 비트로 매핑
- shuffle_order_engine    : 마지막 값의 내부 버퍼를 유지해 난수의 순서를 수정

인스턴스화 및 적용을 통해 기본 엔진에서 빌드한 미리 적용 및 정의된 엔진
타입 정의
- knuth_b
- minstd_rand
- minstd_rand0
- mt19937
- mt19937_64
- ranlux24
- ranlux24_base
- ranlux48
- ranlux48_base
distribution

분포 클래스는 generator 개체가 생성한 부호 없는 정수를 매개변수화된 분포에 매핑한다.
I는 정수 타입, R은 실수 타입을 의미
uniform_int_distribution<I>(a=0, b=max);
uniform_real_distribution<R>(a=0.0, b=1.0);
bernoulli_distribution(p=0.5);
binormal_distribution<I>(t=1, p=0.5);
geometric_distribution<I>(p=0.5);
negative_binormal_distribution<I>(k=1, p=0.5);
poisson_distribution<I>(m=1.0);
exponetial_distribution<R>(l=1.0);
gamma_distribution<R, R>(a=1.0, b=1.0);
weibull_distribution<R>(a=1.0, b=1.0);
extreme_value_distribution<R>(a=0.0, b=1.0);
normal_distribution<R>(m=0.0, s=1.0);
lognormal_distribution<R>(m=0.0, s=1.0);
chi_squared_distribution<R>(n=1);
cauchy_distribution<R>(a=0.0, b=1.0);
fisher_f_distribution<R>(m=1, n=1);
student_t_distribution<R>(n=1);
discrete_distribution<I>(b, e);
piece_constant_distribution<R>(b, e, b2, e2);
piece_linear_distribution<R>(b, e, b2, e2);
분포는 난수 생성기에 의해 매개변수화된다.
default_random_engine re(random_device{}());
normal_distribution<> normal;
for(int i=0; i<6; ++i) cout<<normal(re)<<endl;
위과 같은 작업(generator의 난수를 매개변수화)을 <functional> 헤더의 bind()함수를 이용해서 결속시킬 수 있다.
auto normal=bind(normal_distribution<>{}, default_random_engine(random_device{}()));
for(int i=0; i<6; ++i) cout<<normal()<<endl;
혹은 람다 표현식을 사용
auto normal=[re=default_random_engine(random_device{}()), n=normal_distribution<>{}](void) mutable {return n(re); };
주가 변동 확률적 시뮬레이션
Black-Scholes 모델
default_random_engine re(random_device{}());
normal_distribution<> normal;
const double mu=0.05, sigma=0.3, delta=0.5, years=20.01, a=sigma*sqrt(delta), b=delta*(mu-0.5*sigma*sigma);
vector<double> s={345.2}; //
for(double t=0.0; t<years; t+=delta) s.push_back(s.back()*exp(a*normal(re)+b));